This notebook outlines my process of generating models regarding champion statistics. This notebook is dependent on the data table gameInfo generated from DataExtraction.RMD.

Packages

library(plotly)
library(RColorBrewer)
Warning: package ‘RColorBrewer’ was built under R version 4.1.1
library(data.table)
data.table 1.14.0 using 4 threads (see ?getDTthreads).  Latest news: r-datatable.com

Attaching package: ‘data.table’

The following objects are masked from ‘package:dplyr’:

    between, first, last

The following object is masked from ‘package:purrr’:

    transpose
library(cluster)

Changing Data Structure and Making a List to Store Results

gameInfo.win <- gameInfo %>% 
  mutate(win = as.logical(win)) # Changing to logical to make future code easier
# # Commenting out to make sure that I don't run over my cashed results
# winrate <- list(
#   tables = list(),
#   plots = list()
# )

Overall Champion Winrate

winrate$tables$base <- gameInfo.win %>% 
  group_by(championName) %>% 
  summarize(winrate = mean(win), games = n(), .groups = "drop")

head(winrate$tables$base)

Lux is very popular and has a high win rate. Also yeesh, Ryze has a really low win rate in Season 11. Maybe we can see a bit more when we include the tier of play. Weirdly a lot of marksmen in the very popular category. From inspection there might be three groups of champions: 1. Balanced with medium play rate 2. Balanced with high play rate 3. Under tuned with low play rate My worry with a k-means is that the relative importance games played will overshadow the winrate statistic - maybe we can normalize the dataset.

Adding Tier Information

winrate$tables$tier <- gameInfo.win %>% 
  group_by(championName, tier) %>% 
  summarize(winrate = mean(win), games = n(), .groups = "drop")

head(winrate$tables$tier)
winrate$plots$tier <- winrate$table$tier %>% 
  plot_ly(
    x = ~games,
    y = ~winrate,
    color = ~tier,
    text = ~championName,
    type = "scatter",
    mode = "markers"
  )

winrate$plots$tier

Well we can for sure see regression towards the mean with a higher number of games wit ha few notable exceptions: 1. Rell is popping off in Diamond but likely variance due to low game number. Similar with Ornn in Iron. 2. One notable outlier that can be viewed instantly is Iron Yuumi, with a winrate of 0.434 which matches intuition. 3. Similarly, Ryze, Zoe, and Gwen have horrible win rates in bronze for the number of games. However this also might be due to oversampling of particular players in the scraping process.

A few ADC’s also grab my attention - Ezreal and Lucian seem to have an especially low winrate in Diamond. Might be worth investigating. This data set doesn’t have any obvious clusters to my eyes, let’s try running a kmeans clustering algorithm on the overall play rates.

K-means Clustering

Normalize Data Set and run kmeans models

It would appear that 3 or 4 clusters is optimal - let’s plot them: ## Plotting ### Setting up Data Table

Graphing Method 1

# Will use this in the future, even if there is that ugly play button
winrate$tables$clustered %>% 
  pivot_longer(cols = 4:11, names_to = "n_clusters", values_to = "membership") %>%
  plot_ly(
    x = ~games,
    y = ~winrate,
    colors = "Set3",
    color = ~membership,
    frame = ~n_clusters,
    type = "scatter",
    mode = "markers",
    text = ~championName
  ) %>% 
  layout(
    title = "Cluster Membership",
    xaxis = list(title = "Games Played"),
    yaxis = list(title = "Winrate")
  ) %>% 
  animation_opts(
    frame = 100
  )

Graphing Method 2 (Really annoying but better plot)

The 3 cluster model was too general and the 4 cluster model identifies an under performing group but includes champions like Cassiopiea with a decent win rate (0.495) in this group. The 5 cluster model appears to capture this under performing group of champions without gross overestimating. This is presuming that there is indeed an underlying structure to this space which may not be the case. It may be the case that a more complex space may yield more representative results as k-means clustering uses euclidean distance.

It’s also unclear whether or not the low play rate is a cause of the low win rate or because of champion imbalance. Optimal prescriptive balance changes might simply be a simple binary classifier in which champions below a certain win rate need buffs and the converse for high win rate champions.

So low play rate might be caused by a few factors: 1. The champion is weak and thus people avoid playing them. Champion weakness can be because: A. The champion cannot carry even when given gold B. They have a hard time acquiring gold Note that these factors could be cause by external factors like the metacgame or item changes. 2. The champion is difficult or un-fun and thus fewer players choose to play said champion.

To test hypothesis 1, we can control for gold and examine the winrates of the lowest winrate champions. If the champion’s winrate is no different from the null hypothesis (50%) at higher gold values, then sub hypothesis A is false. From there, we can look at the average gold distribution for that champion, and if it is significantly lower than the null hypothesis, then hypothesis B is true.

If both hypotheses are false, then we can use either internal or external (Mobalytics) measures of difficulty to determine the accuracy of hypothesis 2.

Preparation

High / Low WR table of champions

Let’s take the 3 champions per role with the lowest winrates and compare them to the top 3 winrate champions by role.

Function To Execute Model Generation

GET_WR_DATA <- function(ROLE, TABLE1 = data.temp$goldPercent, TABLE2 = winrate$tables$highlow){
  
  glm_data <- TABLE1 %>% 
    filter(
      championName %in% (TABLE2 %>% 
        filter(individualPosition == ROLE) %>% 
        {.$championName})
    ) %>% 
    return()
  
}

GET_WR_MODEL <- function(table){
  
  glm(win ~ championName*goldEarned, data = table, family = "binomial") %>% 
    return()
  
}

GET_WR_PLOTS <- function(ROLE, table, table2 = winrate$tables$highlow, ...){
  
  models <- list()
  preds <- list()
  goldRange <- data.frame(goldEarned = seq(0, .75, length.out = 75))
  
  champions <- table %>% 
    distinct(championName) %>% 
    left_join(
      table2, 
      by = "championName"
    )

  
  plot <- plot_ly()
  
  for(i in 1:nrow(champions)){
    
    models[[i]] <- table %>% 
      filter(championName == champions$championName[[i]]) %>% 
      filter(individualPosition == ROLE) %>% 
      glm(formula = win~goldEarned, family = "binomial", data = .)
    
    preds[[i]] <- predict.glm(models[[i]], type = "response", newdata = goldRange)
    
    if(champions$win[[i]] == "high"){
      
    plot <- plot %>% 
      add_trace(
        x = goldRange$goldEarned,
        y = preds[[i]],
        type = "scatter",
        mode = "lines",
        name = champions$championName[[i]]
      )
    } else if(champions$win[[i]] == "low"){
      
      plot <- plot %>% 
      add_trace(
        x = goldRange$goldEarned,
        y = preds[[i]],
        type = "scatter",
        mode = "lines",
        name = champions$championName[[i]],
        line = list(dash = "dash")
      )
      
    }
    
  }
  
  plot <- plot %>% 
    layout(
      xaxis = list(title = "Gold Percent"),
      yaxis = list(title ="Predicted Probability of Winning"),
      title = paste0("Top and Bottom 3 Winrate Champions (", str_to_title(ROLE), ")")
    )
  
  return(plot)
}

Observing Winrate when Controlling for Gold Percent

winrate$tables$WR_highlow$plot
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]
NA

Ah right, this is hard to interpret as it’s already been shown that gold percent doesn’t usually make a huge difference on position other than bottom. There are some interesting conclusions to be made however:

  1. Interpreting the ADC slot however, we observe that the highest winrate champions are the ones that are strong with or without gold, thus the conclusion is that these champions have a low win rate because they have a hard time acquiring gold. 2. Observing the top lane which has been shown to be generally insensitive to gold percent - champions like Rekenton and Gwen seem to be very good at carrying when they have a large percentage of team gold but are very unlikely to win if they are not the champion fed resources. Gangplank has the opposite problem in that he does worse the more fed he is relative to his teammates, meaning that he has a hard time solo carrying a game.
  2. An interesting insight that can be made from the jungle position is that Master Yi (as expected) is a very hard carry if he can get the ball rolling, which it might appear that he does as he is played significantly more in lower elos. Conversely, champions like Skarner and Rammus do very poorly with gold but have a high win rate likely to do their ability to feed gold to their laners and their relatively easy kit.
  3. Overall we observe that a champions on the extremes of winrate are well modeled by their difficulty - an emergent hypothesis: the additional cognitive load incurred by playing a high difficulty champions will reduce performance and thus adversely affect winrate.

Observing Play rate / Difficulty Metrics

Difficulty and Winrate

winrate$tables$difficulty <- winrate$tables$base %>% 
  left_join(
    champions.scraped %>% 
      select(!tag),
    by = c("championName" = "name")
  )

winrate$tables$difficulty %>%
  group_by(difficulty) %>% 
  summarize(sd = sd(winrate), winrate = mean(winrate), n = n(), .groups = "drop") %>% 
  mutate(sem = sd / sqrt(n)) %>% 
  ggplot(aes(x = difficulty, y = winrate)) +
  geom_point() +
  geom_errorbar(aes(ymin = winrate - sem, ymax = winrate + sem), width = .3)

ANOVA and Post-Hoc Tests

So insignificant differences with the exception of the severe comparisons. Graphical analysis does show slight advantages to easy champions.

Takeaways: 1. As an ADC, don’t play hyper carries, your champ should do well with or without gold e.g., Miss Fortune, Ashe. 2. As a jungler - if you’re going to play a carry champion, make sure you set yourself to get ahead early or if you’re playing a supportive jungler, make sure you get your lanes ahead. 3. As a top laner, if you’re playing a hyper carry, cry to your jungler and mid to camp your lane becuase it’s going to be a bad time if you get behind. 4. As a mid laner, play easier champions and try to get your lanes ahead. Mid doesn’t carry as much when you have too much gold relative to your team. 5. As a support, play easier champions and unless you’re playing pyke, give the gold to your team and ward a lot more.

Covariate Champion Winrate

Are there any particular combinations of champions which are truly degenerate (see Master Yi - Taric funneling which was a gigantic issue in previous seasons) ## Temporary Table to Make Funciton More Efficient

data.temp$champTeams <- gameInfo.win %>% 
  select(match, win, championName) %>%
  group_by(match, win) %>% 
  mutate(champion = row_number()) %>% 
  pivot_wider(
    names_from = champion,
    values_from = championName
  ) %>% 
  mutate(team = str_c("_",`2`,`3`,`4`,`5`,"_", sep = "_")) %>% 
  select(match, win, champion = `1`, team) %>% 
  ungroup()

Executing Function

winrate$tables$covariate.temp <- expand.grid(
  champions.scraped$name,
  champions.scraped$name
) %>%
  apply(., 1, sort) %>% # Removing rows symmetrically, no need to double search
  t() %>% 
  unique() %>% 
  as_tibble() %>% 
  rename(Champ1 = V1, Champ2 = V2) %>% 
  filter(Champ1 != Champ2) # Removing diagonal elements

# This takes SO damn long to run, I wonder if there is a better way to do this...
# Doing this in two steps to avoid running long functions again
winrate$tables$covariate.temp2 <- winrate$tables$covariate.temp %>%
  mutate(
    gameList = pmap(
      .,
      .f = function(Champ1, Champ2, TABLE = data.temp$champTeams){

        TABLE %>%
          group_by(match, win) %>%
          filter(champion == Champ1) %>% # Okay this is way faster but still turbo slow
          filter(str_detect(team, paste0("_", Champ2, "_"))) %>%
          ungroup() %>%
          return()

      }
    )
  )

winrate$tables$covariate <- winrate$tables$covariate.temp2 %>% 
  mutate(
    data = map(
      gameList,
      .f = function(gameList){
        
        gameList %>% 
          summarize(winrate = mean(win), games = n()) %>% 
          return()
        
      }
    )
  ) %>% 
  unnest(cols = c(data))

# Making a new table to make searching particular champion pairs easier
winrate$tables$covariate.search <- bind_rows(
  winrate$tables$covariate,
  winrate$tables$covariate %>% 
    rename(Champ2 = 1, Champ1 = 2)
)


winrate$plots$covariate <- winrate$tables$covariate %>% 
  plot_ly(
    x = ~games,
    y = ~winrate,
    text = ~paste(Champ1, Champ2, sep = " "),
    type = "scatter",
    mode = "markers"
  )

winrate$plots$covariate 

Probably not enough games to make any predictions - with 155*154 champion duo combinations, would need a lot more data to have a decent sample of all combinations.

Next, I’ll be attempting to use a neural network to make predictions.

LS0tDQp0aXRsZTogIkNoYW1waW9uIFN0YXRpc3RpY3MiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGlzIG5vdGVib29rIG91dGxpbmVzIG15IHByb2Nlc3Mgb2YgZ2VuZXJhdGluZyBtb2RlbHMgcmVnYXJkaW5nIGNoYW1waW9uIHN0YXRpc3RpY3MuIFRoaXMgbm90ZWJvb2sgaXMgZGVwZW5kZW50IG9uIHRoZSBkYXRhIHRhYmxlIGdhbWVJbmZvIGdlbmVyYXRlZCBmcm9tIERhdGFFeHRyYWN0aW9uLlJNRC4NCg0KIyBQYWNrYWdlcw0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGNsdXN0ZXIpDQpgYGANCg0KIyBDaGFuZ2luZyBEYXRhIFN0cnVjdHVyZSBhbmQgTWFraW5nIGEgTGlzdCB0byBTdG9yZSBSZXN1bHRzDQpgYGB7cn0NCmdhbWVJbmZvLndpbiA8LSBnYW1lSW5mbyAlPiUgDQogIG11dGF0ZSh3aW4gPSBhcy5sb2dpY2FsKHdpbikpICMgQ2hhbmdpbmcgdG8gbG9naWNhbCB0byBtYWtlIGZ1dHVyZSBjb2RlIGVhc2llcg0KIyAjIENvbW1lbnRpbmcgb3V0IHRvIG1ha2Ugc3VyZSB0aGF0IEkgZG9uJ3QgcnVuIG92ZXIgbXkgY2FzaGVkIHJlc3VsdHMNCiMgd2lucmF0ZSA8LSBsaXN0KA0KIyAgIHRhYmxlcyA9IGxpc3QoKSwNCiMgICBwbG90cyA9IGxpc3QoKQ0KIyApDQpgYGANCg0KIyBPdmVyYWxsIENoYW1waW9uIFdpbnJhdGUNCmBgYHtyfQ0Kd2lucmF0ZSR0YWJsZXMkYmFzZSA8LSBnYW1lSW5mby53aW4gJT4lIA0KICBncm91cF9ieShjaGFtcGlvbk5hbWUpICU+JSANCiAgc3VtbWFyaXplKHdpbnJhdGUgPSBtZWFuKHdpbiksIGdhbWVzID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKQ0KDQpoZWFkKHdpbnJhdGUkdGFibGVzJGJhc2UpDQpgYGANCmBgYHtyfQ0Kd2lucmF0ZSRwbG90cyRiYXNlIDwtIHdpbnJhdGUkdGFibGVzJGJhc2UgJT4lIA0KICBsZWZ0X2pvaW4oDQogICAgY2hhbXBpb25zLnNjcmFwZWQsDQogICAgYnkgPSBjKCJjaGFtcGlvbk5hbWUiID0gIm5hbWUiKQ0KICApICU+JSANCiAgcGxvdF9seSgNCiAgICB4ID0gfmdhbWVzLA0KICAgIHkgPSB+d2lucmF0ZSwNCiAgICBjb2xvciA9IH50YWcsDQogICAgdGV4dCA9IH5jaGFtcGlvbk5hbWUsDQogICAgdHlwZSA9ICJzY2F0dGVyIiwNCiAgICBtb2RlID0gIm1hcmtlcnMiDQogICkNCg0Kd2lucmF0ZSRwbG90cyRiYXNlDQpgYGANCkx1eCBpcyB2ZXJ5IHBvcHVsYXIgYW5kIGhhcyBhIGhpZ2ggd2luIHJhdGUuIEFsc28geWVlc2gsIFJ5emUgaGFzIGEgcmVhbGx5IGxvdyB3aW4gcmF0ZSBpbiBTZWFzb24gMTEuIE1heWJlIHdlIGNhbiBzZWUgYSBiaXQgbW9yZSB3aGVuIHdlIGluY2x1ZGUgdGhlIHRpZXIgb2YgcGxheS4gV2VpcmRseSBhIGxvdCBvZiBtYXJrc21lbiBpbiB0aGUgdmVyeSBwb3B1bGFyIGNhdGVnb3J5LiBGcm9tIGluc3BlY3Rpb24gdGhlcmUgbWlnaHQgYmUgdGhyZWUgZ3JvdXBzIG9mIGNoYW1waW9uczoNCjEuIEJhbGFuY2VkIHdpdGggbWVkaXVtIHBsYXkgcmF0ZQ0KMi4gQmFsYW5jZWQgd2l0aCBoaWdoIHBsYXkgcmF0ZQ0KMy4gVW5kZXIgdHVuZWQgd2l0aCBsb3cgcGxheSByYXRlDQpNeSB3b3JyeSB3aXRoIGEgay1tZWFucyBpcyB0aGF0IHRoZSByZWxhdGl2ZSBpbXBvcnRhbmNlIGdhbWVzIHBsYXllZCB3aWxsIG92ZXJzaGFkb3cgdGhlIHdpbnJhdGUgc3RhdGlzdGljIC0gbWF5YmUgd2UgY2FuIG5vcm1hbGl6ZSB0aGUgZGF0YXNldC4NCg0KIyBBZGRpbmcgVGllciBJbmZvcm1hdGlvbg0KYGBge3J9DQp3aW5yYXRlJHRhYmxlcyR0aWVyIDwtIGdhbWVJbmZvLndpbiAlPiUgDQogIGdyb3VwX2J5KGNoYW1waW9uTmFtZSwgdGllcikgJT4lIA0KICBzdW1tYXJpemUod2lucmF0ZSA9IG1lYW4od2luKSwgZ2FtZXMgPSBuKCksIC5ncm91cHMgPSAiZHJvcCIpDQoNCmhlYWQod2lucmF0ZSR0YWJsZXMkdGllcikNCmBgYA0KYGBge3J9DQp3aW5yYXRlJHBsb3RzJHRpZXIgPC0gd2lucmF0ZSR0YWJsZSR0aWVyICU+JSANCiAgcGxvdF9seSgNCiAgICB4ID0gfmdhbWVzLA0KICAgIHkgPSB+d2lucmF0ZSwNCiAgICBjb2xvciA9IH50aWVyLA0KICAgIHRleHQgPSB+Y2hhbXBpb25OYW1lLA0KICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgbW9kZSA9ICJtYXJrZXJzIg0KICApDQoNCndpbnJhdGUkcGxvdHMkdGllcg0KYGBgDQpXZWxsIHdlIGNhbiBmb3Igc3VyZSBzZWUgcmVncmVzc2lvbiB0b3dhcmRzIHRoZSBtZWFuIHdpdGggYSBoaWdoZXIgbnVtYmVyIG9mIGdhbWVzIHdpdCBoYSBmZXcgbm90YWJsZSBleGNlcHRpb25zOg0KMS4gUmVsbCBpcyBwb3BwaW5nIG9mZiBpbiBEaWFtb25kIGJ1dCBsaWtlbHkgdmFyaWFuY2UgZHVlIHRvIGxvdyBnYW1lIG51bWJlci4gU2ltaWxhciB3aXRoIE9ybm4gaW4gSXJvbi4gDQoyLiBPbmUgbm90YWJsZSBvdXRsaWVyIHRoYXQgY2FuIGJlIHZpZXdlZCBpbnN0YW50bHkgaXMgSXJvbiBZdXVtaSwgd2l0aCBhIHdpbnJhdGUgb2YgMC40MzQgd2hpY2ggbWF0Y2hlcyBpbnR1aXRpb24uIA0KMy4gU2ltaWxhcmx5LCBSeXplLCBab2UsIGFuZCBHd2VuIGhhdmUgaG9ycmlibGUgd2luIHJhdGVzIGluIGJyb256ZSBmb3IgdGhlIG51bWJlciBvZiBnYW1lcy4gSG93ZXZlciB0aGlzIGFsc28gbWlnaHQgYmUgZHVlIHRvIG92ZXJzYW1wbGluZyBvZiBwYXJ0aWN1bGFyIHBsYXllcnMgaW4gdGhlIHNjcmFwaW5nIHByb2Nlc3MuIA0KDQpBIGZldyBBREMncyBhbHNvIGdyYWIgbXkgYXR0ZW50aW9uIC0gRXpyZWFsIGFuZCBMdWNpYW4gc2VlbSB0byBoYXZlIGFuIGVzcGVjaWFsbHkgbG93IHdpbnJhdGUgaW4gRGlhbW9uZC4gTWlnaHQgYmUgd29ydGggaW52ZXN0aWdhdGluZy4gVGhpcyBkYXRhIHNldCBkb2Vzbid0IGhhdmUgYW55IG9idmlvdXMgY2x1c3RlcnMgdG8gbXkgZXllcywgbGV0J3MgdHJ5IHJ1bm5pbmcgYSBrbWVhbnMgY2x1c3RlcmluZyBhbGdvcml0aG0gb24gdGhlIG92ZXJhbGwgcGxheSByYXRlcy4NCg0KIyBLLW1lYW5zIENsdXN0ZXJpbmcNCiMjIE5vcm1hbGl6ZSBEYXRhIFNldCBhbmQgcnVuIGttZWFucyBtb2RlbHMNCmBgYHtyfQ0Kd2lucmF0ZSR0YWJsZXMkYmFzZS5ub3JtYWwgPC0gd2lucmF0ZSR0YWJsZXMkYmFzZSAlPiUgDQogIG11dGF0ZSh3aW5yYXRlID0gKHdpbnJhdGUgLSBtZWFuKHdpbnJhdGUpKS9zZCh3aW5yYXRlKSwgZ2FtZXMgPSAoZ2FtZXMgLSBtZWFuKGdhbWVzKSkvc2QoZ2FtZXMpKQ0KDQpyZXN1bHRzJG1vZGVscyR3aW5wbGF5JGttZWFucyA8LSBsaXN0KCkgIyBMaXN0IHRvIHN0b3JlIGstbWVhbnMgbW9kZWxzDQpyZXN1bHRzJG1vZGVscyR3aW5wbGF5JHNpbGhvdWV0dGUgPC0gdGliYmxlKGsgPSAyOjksIHN1bW9mc3EgPSByZXAoMCw4KSkNCg0Kc2V0LnNlZWQoMSkNCmZvcihpIGluIDE6OCl7DQogIA0KICByZXN1bHRzJG1vZGVscyR3aW5wbGF5JGttZWFuc1tbaV1dIDwtIGttZWFucyh3aW5yYXRlJHRhYmxlcyRiYXNlLm5vcm1hbCAlPiUgc2VsZWN0KCFjaGFtcGlvbk5hbWUpLCBjZW50ZXJzID0gaSArIDEsIG5zdGFydCA9IDUwKQ0KICANCiAgcmVzdWx0cyRtb2RlbHMkd2lucGxheSRzaWxob3VldHRlJHN1bW9mc3FbW2ldXSA8LSByZXN1bHRzJG1vZGVscyR3aW5wbGF5JGttZWFuc1tbaV1dJHRvdC53aXRoaW5zcw0KICANCn0NCnJtKGkpDQpyZXN1bHRzJG1vZGVscyR3aW5wbGF5JHNpbGhvdWV0dGUgJT4lIA0KICBwbG90X2x5KA0KICAgIHggPSB+aywNCiAgICB5ID0gfnN1bW9mc3EsDQogICAgdHlwZSA9ICJzY2F0dGVyIiwNCiAgICBtb2RlID0gImxpbmVzK21hcmtlcnMiDQogICkNCmBgYA0KSXQgd291bGQgYXBwZWFyIHRoYXQgMyBvciA0IGNsdXN0ZXJzIGlzIG9wdGltYWwgLSBsZXQncyBwbG90IHRoZW06DQojIyBQbG90dGluZw0KIyMjIFNldHRpbmcgdXAgRGF0YSBUYWJsZQ0KYGBge3J9DQpkYXRhLnRlbXAkY2x1c3Rlck1lbWJlcnNoaXAgPC0gdGliYmxlKGJhc2UgPSByZXAoMCwxNTcpKQ0KZm9yKGkgaW4gMTpsZW5ndGgocmVzdWx0cyRtb2RlbHMkd2lucGxheSRrbWVhbnMpKXsNCiAgDQogIGRhdGEudGVtcCRjbHVzdGVyTWVtYmVyc2hpcFssaV0gPC0gcmVzdWx0cyRtb2RlbHMkd2lucGxheSRrbWVhbnNbW2ldXSRjbHVzdGVyDQogIA0KfSANCnJtKGkpDQpkYXRhLnRlbXAkbmFtZXMgPC0gc3RyX2MocmVwKCJrID0gIiksIDI6OSkNCndpbnJhdGUkdGFibGVzJGNsdXN0ZXJlZCA8LSBiaW5kX2NvbHMoDQogICAgd2lucmF0ZSR0YWJsZXMkYmFzZSwNCiAgICBkYXRhLnRlbXAkY2x1c3Rlck1lbWJlcnNoaXAgJT4lIA0KICAgICAgYG5hbWVzPC1gKGRhdGEudGVtcCRuYW1lcykNCiAgKQ0KDQp3aW5yYXRlJHRhYmxlcyRjbHVzdGVyZWQNCmBgYA0KIyMjIEdyYXBoaW5nIE1ldGhvZCAxDQpgYGB7cn0NCiMgV2lsbCB1c2UgdGhpcyBpbiB0aGUgZnV0dXJlLCBldmVuIGlmIHRoZXJlIGlzIHRoYXQgdWdseSBwbGF5IGJ1dHRvbg0Kd2lucmF0ZSR0YWJsZXMkY2x1c3RlcmVkICU+JSANCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSA0OjExLCBuYW1lc190byA9ICJuX2NsdXN0ZXJzIiwgdmFsdWVzX3RvID0gIm1lbWJlcnNoaXAiKSAlPiUNCiAgcGxvdF9seSgNCiAgICB4ID0gfmdhbWVzLA0KICAgIHkgPSB+d2lucmF0ZSwNCiAgICBjb2xvcnMgPSAiU2V0MyIsDQogICAgY29sb3IgPSB+bWVtYmVyc2hpcCwNCiAgICBmcmFtZSA9IH5uX2NsdXN0ZXJzLA0KICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgbW9kZSA9ICJtYXJrZXJzIiwNCiAgICB0ZXh0ID0gfmNoYW1waW9uTmFtZQ0KICApICU+JSANCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gIkNsdXN0ZXIgTWVtYmVyc2hpcCIsDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIkdhbWVzIFBsYXllZCIpLA0KICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJXaW5yYXRlIikNCiAgKSAlPiUgDQogIGFuaW1hdGlvbl9vcHRzKA0KICAgIGZyYW1lID0gMTAwDQogICkNCmBgYA0KDQojIyMgR3JhcGhpbmcgTWV0aG9kIDIgKFJlYWxseSBhbm5veWluZyBidXQgYmV0dGVyIHBsb3QpDQpgYGB7cn0NCnBsb3RseV9hcmdzIDwtIGxpc3QoKSAjIExpc3QgdG8gc3RvcmUgcGxvdGx5IGFyZ3VtZW50cw0KcGxvdGx5X2FyZ3MkdHJhY2VzIDwtIGxpc3QoKSAjIExpc3Qgb2YgcGxvdHMgDQoNCmZvcihpIGluIDE6bGVuZ3RoKHJlc3VsdHMkbW9kZWxzJHdpbnBsYXkka21lYW5zKSl7DQogIA0KICBwbG90bHlfYXJncyR0cmFjZXNbW2ldXSA8LSBsaXN0KA0KICAgIHZpc2libGUgPSBGLA0KICAgIG5hbWUgPSBpKzEsDQogICAgeCA9IHdpbnJhdGUkdGFibGVzJGNsdXN0ZXJlZCRnYW1lcywNCiAgICB5ID0gd2lucmF0ZSR0YWJsZXMkY2x1c3RlcmVkJHdpbnJhdGUsDQogICAgdGV4dCA9IHdpbnJhdGUkdGFibGVzJGNsdXN0ZXJlZCRjaGFtcGlvbk5hbWUsDQogICAgY29sb3IgPSB3aW5yYXRlJHRhYmxlcyRjbHVzdGVyZWRbLGkrM11bWzFdXSwNCiAgICBjb2xvcnMgPSAiU2V0MyINCiAgKQ0KICANCn0NCg0KcGxvdGx5X2FyZ3MkdHJhY2VzWzJdW1sxXV0kdmlzaWJsZSA9IFQgIyBNYW51YWxseSBzZXR0aW5nIHRoZSBrPTMgcGxvdCB0byBiZSB2aXNpYmxlIGZpcnN0DQoNCnBsb3RseV9hcmdzJHN0ZXBzIDwtIGxpc3QoKSAjIExpc3QgdG8gc3RvcmUgb2JqZWN0cyBwb3B1bGF0ZWQgYnkgbG9vcA0KZGF0YS50ZW1wJGZpZyA8LSBwbG90X2x5KCkNCg0KZm9yKGkgaW4gMTpsZW5ndGgocmVzdWx0cyRtb2RlbHMkd2lucGxheSRrbWVhbnMpKXsNCiAgDQogIGRhdGEudGVtcCRmaWcgPC0gYWRkX21hcmtlcnMoDQogICAgZGF0YS50ZW1wJGZpZywNCiAgICB4ID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0keCwNCiAgICB5ID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0keSwNCiAgICBjb2xvciA9IHBsb3RseV9hcmdzJHRyYWNlc1tpXVtbMV1dJGNvbG9yLA0KICAgIGNvbG9ycyA9IHBsb3RseV9hcmdzJHRyYWNlc1tpXVtbMV1dJGNvbG9ycywNCiAgICB2aXNpYmxlID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0kdmlzaWJsZSwNCiAgICBuYW1lID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0kbmFtZSwNCiAgICB0ZXh0ID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0kdGV4dCwNCiAgICB0eXBlID0gInNjYXR0ZXIiLA0KICAgIG1vZGUgPSAibWFya2VycyIsDQogICAgc2hvd2xlZ2VuZCA9IEYNCiAgKQ0KICANCiAgcGxvdGx5X2FyZ3Mkc3RlcCA8LSBsaXN0KA0KICAgIGFyZ3MgPSBsaXN0KA0KICAgICAgInZpc2libGUiLA0KICAgICAgcmVwKEYsIGxlbmd0aChwbG90bHlfYXJncyR0cmFjZXMpKQ0KICAgICksDQogICAgbWV0aG9kID0gInJlc3R5bGUiLA0KICAgIGxhYmVsID0gcGxvdGx5X2FyZ3MkdHJhY2VzW2ldW1sxXV0kbmFtZQ0KICApDQogIA0KICBwbG90bHlfYXJncyRzdGVwJGFyZ3NbWzJdXVtpXSA9IFQNCiAgcGxvdGx5X2FyZ3Mkc3RlcHNbW2ldXSA9IHBsb3RseV9hcmdzJHN0ZXANCiAgDQp9DQpybShpKQ0KDQp3aW5yYXRlJHBsb3RzJGttZWFucyA8LSBkYXRhLnRlbXAkZmlnICU+JSANCiAgbGF5b3V0KA0KICAgIHNsaWRlcnMgPSBsaXN0KGxpc3QoDQogICAgICBhY3RpdmUgPSAxLCAjIDAgaW5kZXhlZCBpbiBSLCBuaWNlDQogICAgICBjdXJyZW50dmFsdWUgPSBsaXN0KHByZWZpeCA9ICJrID0gIiksDQogICAgICBzdGVwcyA9IHBsb3RseV9hcmdzJHN0ZXBzLA0KICAgICAgcGFkID0gbGlzdCh0ID0gNDUpDQogICAgKSkNCiAgKSAlPiUgDQogIGxheW91dCgNCiAgICB0aXRsZSA9ICJDbHVzdGVyIE1lbWJlcnNoaXAiLA0KICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJHYW1lcyBQbGF5ZWQiKSwNCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiV2lucmF0ZSIpDQogICkNCg0Kd2lucmF0ZSRwbG90cyRrbWVhbnMNCmBgYA0KVGhlIDMgY2x1c3RlciBtb2RlbCB3YXMgdG9vIGdlbmVyYWwgYW5kIHRoZSA0IGNsdXN0ZXIgbW9kZWwgaWRlbnRpZmllcyBhbiB1bmRlciBwZXJmb3JtaW5nIGdyb3VwIGJ1dCBpbmNsdWRlcyBjaGFtcGlvbnMgbGlrZSBDYXNzaW9waWVhIHdpdGggYSBkZWNlbnQgd2luIHJhdGUgKDAuNDk1KSBpbiB0aGlzIGdyb3VwLiBUaGUgNSBjbHVzdGVyIG1vZGVsIGFwcGVhcnMgdG8gY2FwdHVyZSB0aGlzIHVuZGVyIHBlcmZvcm1pbmcgZ3JvdXAgb2YgY2hhbXBpb25zIHdpdGhvdXQgZ3Jvc3Mgb3ZlcmVzdGltYXRpbmcuIFRoaXMgaXMgcHJlc3VtaW5nIHRoYXQgdGhlcmUgKmlzKiBpbmRlZWQgYW4gdW5kZXJseWluZyBzdHJ1Y3R1cmUgdG8gdGhpcyBzcGFjZSB3aGljaCBtYXkgbm90IGJlIHRoZSBjYXNlLiBJdCBtYXkgYmUgdGhlIGNhc2UgdGhhdCBhIG1vcmUgY29tcGxleCBzcGFjZSBtYXkgeWllbGQgbW9yZSByZXByZXNlbnRhdGl2ZSByZXN1bHRzIGFzIGstbWVhbnMgY2x1c3RlcmluZyB1c2VzIGV1Y2xpZGVhbiBkaXN0YW5jZS4gDQoNCkl0J3MgYWxzbyB1bmNsZWFyIHdoZXRoZXIgb3Igbm90IHRoZSBsb3cgcGxheSByYXRlIGlzIGEgY2F1c2Ugb2YgdGhlIGxvdyB3aW4gcmF0ZSBvciBiZWNhdXNlIG9mIGNoYW1waW9uIGltYmFsYW5jZS4gT3B0aW1hbCBwcmVzY3JpcHRpdmUgYmFsYW5jZSBjaGFuZ2VzIG1pZ2h0IHNpbXBseSBiZSBhIHNpbXBsZSBiaW5hcnkgY2xhc3NpZmllciBpbiB3aGljaCBjaGFtcGlvbnMgYmVsb3cgYSBjZXJ0YWluIHdpbiByYXRlIG5lZWQgYnVmZnMgYW5kIHRoZSBjb252ZXJzZSBmb3IgaGlnaCB3aW4gcmF0ZSBjaGFtcGlvbnMuDQoNClNvIGxvdyBwbGF5IHJhdGUgbWlnaHQgYmUgY2F1c2VkIGJ5IGEgZmV3IGZhY3RvcnM6DQoxLiBUaGUgY2hhbXBpb24gaXMgd2VhayBhbmQgdGh1cyBwZW9wbGUgYXZvaWQgcGxheWluZyB0aGVtLiBDaGFtcGlvbiB3ZWFrbmVzcyBjYW4gYmUgYmVjYXVzZToNCiAgQS4gVGhlIGNoYW1waW9uIGNhbm5vdCBjYXJyeSBldmVuIHdoZW4gZ2l2ZW4gZ29sZA0KICBCLiBUaGV5IGhhdmUgYSBoYXJkIHRpbWUgYWNxdWlyaW5nIGdvbGQNCiAgTm90ZSB0aGF0IHRoZXNlIGZhY3RvcnMgY291bGQgYmUgY2F1c2UgYnkgZXh0ZXJuYWwgZmFjdG9ycyBsaWtlIHRoZSBtZXRhY2dhbWUgb3IgaXRlbSBjaGFuZ2VzLg0KMi4gVGhlIGNoYW1waW9uIGlzIGRpZmZpY3VsdCBvciB1bi1mdW4gYW5kIHRodXMgZmV3ZXIgcGxheWVycyBjaG9vc2UgdG8gcGxheSBzYWlkIGNoYW1waW9uLg0KDQpUbyB0ZXN0IGh5cG90aGVzaXMgMSwgd2UgY2FuIGNvbnRyb2wgZm9yIGdvbGQgYW5kIGV4YW1pbmUgdGhlIHdpbnJhdGVzIG9mIHRoZSBsb3dlc3Qgd2lucmF0ZSBjaGFtcGlvbnMuIElmIHRoZSBjaGFtcGlvbidzIHdpbnJhdGUgaXMgbm8gZGlmZmVyZW50IGZyb20gdGhlIG51bGwgaHlwb3RoZXNpcyAoNTAlKSBhdCBoaWdoZXIgZ29sZCB2YWx1ZXMsIHRoZW4gc3ViIGh5cG90aGVzaXMgQSBpcyBmYWxzZS4gRnJvbSB0aGVyZSwgd2UgY2FuIGxvb2sgYXQgdGhlIGF2ZXJhZ2UgZ29sZCBkaXN0cmlidXRpb24gZm9yIHRoYXQgY2hhbXBpb24sIGFuZCBpZiBpdCBpcyBzaWduaWZpY2FudGx5IGxvd2VyIHRoYW4gdGhlIG51bGwgaHlwb3RoZXNpcywgdGhlbiBoeXBvdGhlc2lzIEIgaXMgdHJ1ZS4gDQoNCklmIGJvdGggaHlwb3RoZXNlcyBhcmUgZmFsc2UsIHRoZW4gd2UgY2FuIHVzZSBlaXRoZXIgaW50ZXJuYWwgb3IgZXh0ZXJuYWwgKE1vYmFseXRpY3MpIG1lYXN1cmVzIG9mIGRpZmZpY3VsdHkgdG8gZGV0ZXJtaW5lIHRoZSBhY2N1cmFjeSBvZiBoeXBvdGhlc2lzIDIuIA0KDQojIyBQcmVwYXJhdGlvbg0KIyMjIEhpZ2ggLyBMb3cgV1IgdGFibGUgb2YgY2hhbXBpb25zDQpMZXQncyB0YWtlIHRoZSAzIGNoYW1waW9ucyBwZXIgcm9sZSB3aXRoIHRoZSBsb3dlc3Qgd2lucmF0ZXMgYW5kIGNvbXBhcmUgdGhlbSB0byB0aGUgdG9wIDMgd2lucmF0ZSBjaGFtcGlvbnMgYnkgcm9sZS4NCmBgYHtyfQ0KIyBDb25zaWRlcmluZyBhIENoYW1waW9uIGFzIGEgUm9sZSBvbmx5IGlmIDc1JSsgb2YgZ2FtZXMgcGxheWVkIGFyZSBvZiB0aGF0IHJvbGUNCmRhdGEudGVtcCRjaGFtcGlvblJvbGVzIDwtIGdhbWVJbmZvLm5vSW52YWxpZCAlPiUgDQogIGdyb3VwX2J5KGNoYW1waW9uTmFtZSkgJT4lIA0KICBjb3VudChpbmRpdmlkdWFsUG9zaXRpb24pICU+JSANCiAgbXV0YXRlKA0KICAgIG4gPSBuL3N1bShuKSwNCiAgICByb2xlID0gY2FzZV93aGVuKA0KICAgICAgbiA+PSAuNzUgIH4gIFRSVUUsDQogICAgICBuIDwgLjc1ICAgfiAgRkFMU0UNCiAgICApDQogICkgJT4lIA0KICBmaWx0ZXIocm9sZSA9PSBUUlVFKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIHNlbGVjdChjaGFtcGlvbk5hbWUsIGluZGl2aWR1YWxQb3NpdGlvbikgJT4lIA0KICBsZWZ0X2pvaW4oDQogICAgd2lucmF0ZSR0YWJsZXMkYmFzZSwNCiAgICBieSA9ICJjaGFtcGlvbk5hbWUiDQogICkgJT4lIA0KICBhcnJhbmdlKHdpbnJhdGUpDQoNCndpbnJhdGUkdGFibGVzJGhpZ2hsb3cgPC0gYmluZF9yb3dzKA0KICBkYXRhLnRlbXAkY2hhbXBpb25Sb2xlcyAlPiUgDQogICAgZ3JvdXBfYnkoaW5kaXZpZHVhbFBvc2l0aW9uKSAlPiUgDQogICAgc2xpY2UoMTozKSAlPiUgDQogICAgbXV0YXRlKHdpbiA9ICJsb3ciKSwNCiAgZGF0YS50ZW1wJGNoYW1waW9uUm9sZXMgJT4lIA0KICAgIGdyb3VwX2J5KGluZGl2aWR1YWxQb3NpdGlvbikgJT4lIA0KICAgIHNsaWNlKChuKCktMik6bigpKSAlPiUgDQogICAgbXV0YXRlKHdpbiA9ICJoaWdoIikNCikgJT4lIA0KICB1bmdyb3VwKCkNCg0Kd2lucmF0ZSR0YWJsZXMkaGlnaGxvdw0KYGBgDQojIyMgRnVuY3Rpb24gVG8gRXhlY3V0ZSBNb2RlbCBHZW5lcmF0aW9uDQpgYGB7cn0NCkdFVF9XUl9EQVRBIDwtIGZ1bmN0aW9uKFJPTEUsIFRBQkxFMSA9IGRhdGEudGVtcCRnb2xkUGVyY2VudCwgVEFCTEUyID0gd2lucmF0ZSR0YWJsZXMkaGlnaGxvdyl7DQogIA0KICBnbG1fZGF0YSA8LSBUQUJMRTEgJT4lIA0KICAgIGZpbHRlcigNCiAgICAgIGNoYW1waW9uTmFtZSAlaW4lIChUQUJMRTIgJT4lIA0KICAgICAgICBmaWx0ZXIoaW5kaXZpZHVhbFBvc2l0aW9uID09IFJPTEUpICU+JSANCiAgICAgICAgey4kY2hhbXBpb25OYW1lfSkNCiAgICApICU+JSANCiAgICByZXR1cm4oKQ0KICANCn0NCg0KR0VUX1dSX01PREVMIDwtIGZ1bmN0aW9uKHRhYmxlKXsNCiAgDQogIGdsbSh3aW4gfiBjaGFtcGlvbk5hbWUqZ29sZEVhcm5lZCwgZGF0YSA9IHRhYmxlLCBmYW1pbHkgPSAiYmlub21pYWwiKSAlPiUgDQogICAgcmV0dXJuKCkNCiAgDQp9DQoNCkdFVF9XUl9QTE9UUyA8LSBmdW5jdGlvbihST0xFLCB0YWJsZSwgdGFibGUyID0gd2lucmF0ZSR0YWJsZXMkaGlnaGxvdywgZ29sZFJhbmdlID0gZGF0YS5mcmFtZShnb2xkRWFybmVkID0gc2VxKDAsIC43NSwgbGVuZ3RoLm91dCA9IDc1KSksIC4uLil7DQogIA0KICBtb2RlbHMgPC0gbGlzdCgpDQogIHByZWRzIDwtIGxpc3QoKQ0KICANCiAgY2hhbXBpb25zIDwtIHRhYmxlICU+JSANCiAgICBkaXN0aW5jdChjaGFtcGlvbk5hbWUpICU+JSANCiAgICBsZWZ0X2pvaW4oDQogICAgICB0YWJsZTIsIA0KICAgICAgYnkgPSAiY2hhbXBpb25OYW1lIg0KICAgICkNCg0KICANCiAgcGxvdCA8LSBwbG90X2x5KCkNCiAgDQogIGZvcihpIGluIDE6bnJvdyhjaGFtcGlvbnMpKXsNCiAgICANCiAgICBtb2RlbHNbW2ldXSA8LSB0YWJsZSAlPiUgDQogICAgICBmaWx0ZXIoY2hhbXBpb25OYW1lID09IGNoYW1waW9ucyRjaGFtcGlvbk5hbWVbW2ldXSkgJT4lIA0KICAgICAgZmlsdGVyKGluZGl2aWR1YWxQb3NpdGlvbiA9PSBST0xFKSAlPiUgDQogICAgICBnbG0oZm9ybXVsYSA9IHdpbn5nb2xkRWFybmVkLCBmYW1pbHkgPSAiYmlub21pYWwiLCBkYXRhID0gLikNCiAgICANCiAgICBwcmVkc1tbaV1dIDwtIHByZWRpY3QuZ2xtKG1vZGVsc1tbaV1dLCB0eXBlID0gInJlc3BvbnNlIiwgbmV3ZGF0YSA9IGdvbGRSYW5nZSkNCiAgICANCiAgICBpZihjaGFtcGlvbnMkd2luW1tpXV0gPT0gImhpZ2giKXsNCiAgICAgIA0KICAgIHBsb3QgPC0gcGxvdCAlPiUgDQogICAgICBhZGRfdHJhY2UoDQogICAgICAgIHggPSBnb2xkUmFuZ2UkZ29sZEVhcm5lZCwNCiAgICAgICAgeSA9IHByZWRzW1tpXV0sDQogICAgICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgICAgIG1vZGUgPSAibGluZXMiLA0KICAgICAgICBuYW1lID0gY2hhbXBpb25zJGNoYW1waW9uTmFtZVtbaV1dDQogICAgICApDQogICAgfSBlbHNlIGlmKGNoYW1waW9ucyR3aW5bW2ldXSA9PSAibG93Iil7DQogICAgICANCiAgICAgIHBsb3QgPC0gcGxvdCAlPiUgDQogICAgICBhZGRfdHJhY2UoDQogICAgICAgIHggPSBnb2xkUmFuZ2UkZ29sZEVhcm5lZCwNCiAgICAgICAgeSA9IHByZWRzW1tpXV0sDQogICAgICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgICAgIG1vZGUgPSAibGluZXMiLA0KICAgICAgICBuYW1lID0gY2hhbXBpb25zJGNoYW1waW9uTmFtZVtbaV1dLA0KICAgICAgICBsaW5lID0gbGlzdChkYXNoID0gImRhc2giKQ0KICAgICAgKQ0KICAgICAgDQogICAgfQ0KICAgIA0KICB9DQogIA0KICBwbG90IDwtIHBsb3QgJT4lIA0KICAgIGxheW91dCgNCiAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJHb2xkIFBlcmNlbnQiKSwNCiAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9IlByZWRpY3RlZCBQcm9iYWJpbGl0eSBvZiBXaW5uaW5nIiksDQogICAgICB0aXRsZSA9IHBhc3RlMCgiVG9wIGFuZCBCb3R0b20gMyBXaW5yYXRlIENoYW1waW9ucyAoIiwgc3RyX3RvX3RpdGxlKFJPTEUpLCAiKSIpDQogICAgKQ0KICANCiAgcmV0dXJuKHBsb3QpDQp9DQpgYGANCg0KDQojIyBPYnNlcnZpbmcgV2lucmF0ZSB3aGVuIENvbnRyb2xsaW5nIGZvciBHb2xkIFBlcmNlbnQNCmBgYHtyfQ0KIyBEb2luZyB0aGlzIGluIHR3byBzdGVwcyB0byBzYXZlIHRoZSBoYXNzbGUgb2YgcmVydW5uaW5nIHdoZW4gdHJvdWJsZXNob290aW5nDQpkYXRhLnRlbXAkV1JfaGlnaGxvdy50ZW1wIDwtIHRpYmJsZSgNCiAgUk9MRSA9IHdpbnJhdGUkdGFibGVzJGhpZ2hsb3cgJT4lIA0KICAgIGRpc3RpbmN0KGluZGl2aWR1YWxQb3NpdGlvbikgJT4lIA0KICAgIHsuJGluZGl2aWR1YWxQb3NpdGlvbn0NCikgJT4lIA0KICBtdXRhdGUoDQogICAgdGFibGUgPSBwbWFwKA0KICAgICAgLiwNCiAgICAgIC5mID0gR0VUX1dSX0RBVEENCiAgICApLA0KICAgIG1vZGVsID0gbWFwKA0KICAgICAgdGFibGUsDQogICAgICAuZiA9IEdFVF9XUl9NT0RFTA0KICAgICkNCiAgKQ0KDQp3aW5yYXRlJHRhYmxlcyRXUl9oaWdobG93IDwtIGRhdGEudGVtcCRXUl9oaWdobG93LnRlbXAgJT4lIA0KICBtdXRhdGUoDQogICAgcGxvdCA9IHBtYXAoDQogICAgICAuLA0KICAgICAgLmYgPSBHRVRfV1JfUExPVFMNCiAgICApDQogICkNCg0Kd2lucmF0ZSR0YWJsZXMkV1JfaGlnaGxvdyRwbG90DQpgYGANCkFoIHJpZ2h0LCB0aGlzIGlzIGhhcmQgdG8gaW50ZXJwcmV0IGFzIGl0J3MgYWxyZWFkeSBiZWVuIHNob3duIHRoYXQgZ29sZCBwZXJjZW50IGRvZXNuJ3QgdXN1YWxseSBtYWtlIGEgaHVnZSBkaWZmZXJlbmNlIG9uIHBvc2l0aW9uIG90aGVyIHRoYW4gYm90dG9tLiBUaGVyZSBhcmUgc29tZSBpbnRlcmVzdGluZyBjb25jbHVzaW9ucyB0byBiZSBtYWRlIGhvd2V2ZXI6DQoNCiAgMS4gSW50ZXJwcmV0aW5nIHRoZSBBREMgc2xvdCBob3dldmVyLCB3ZSBvYnNlcnZlIHRoYXQgdGhlIGhpZ2hlc3Qgd2lucmF0ZSBjaGFtcGlvbnMgYXJlIHRoZSBvbmVzIHRoYXQgYXJlIHN0cm9uZyB3aXRoIG9yIHdpdGhvdXQgZ29sZCwgdGh1cyB0aGUgY29uY2x1c2lvbiBpcyB0aGF0IHRoZXNlIGNoYW1waW9ucyBoYXZlIGEgbG93IHdpbiByYXRlIGJlY2F1c2UgdGhleSBoYXZlIGEgaGFyZCB0aW1lIGFjcXVpcmluZyBnb2xkLiAgICAgICAyLiBPYnNlcnZpbmcgdGhlIHRvcCBsYW5lIHdoaWNoIGhhcyBiZWVuIHNob3duIHRvIGJlIGdlbmVyYWxseSBpbnNlbnNpdGl2ZSB0byBnb2xkIHBlcmNlbnQgLSBjaGFtcGlvbnMgbGlrZSBSZWtlbnRvbiBhbmQgR3dlbiBzZWVtIHRvIGJlIHZlcnkgZ29vZCBhdCBjYXJyeWluZyB3aGVuIHRoZXkgaGF2ZSBhIGxhcmdlIHBlcmNlbnRhZ2Ugb2YgdGVhbSBnb2xkIGJ1dCBhcmUgdmVyeSB1bmxpa2VseSB0byB3aW4gaWYgdGhleSBhcmUgbm90IHRoZSBjaGFtcGlvbiBmZWQgcmVzb3VyY2VzLiBHYW5ncGxhbmsgaGFzIHRoZSBvcHBvc2l0ZSBwcm9ibGVtIGluIHRoYXQgaGUgZG9lcyAqd29yc2UqIHRoZSBtb3JlIGZlZCBoZSBpcyByZWxhdGl2ZSB0byBoaXMgdGVhbW1hdGVzLCBtZWFuaW5nIHRoYXQgaGUgaGFzIGEgaGFyZCB0aW1lIHNvbG8gY2FycnlpbmcgYSBnYW1lLg0KICAzLiBBbiBpbnRlcmVzdGluZyBpbnNpZ2h0IHRoYXQgY2FuIGJlIG1hZGUgZnJvbSB0aGUganVuZ2xlIHBvc2l0aW9uIGlzIHRoYXQgTWFzdGVyIFlpIChhcyBleHBlY3RlZCkgaXMgYSB2ZXJ5IGhhcmQgY2FycnkgaWYgaGUgY2FuIGdldCB0aGUgYmFsbCByb2xsaW5nLCB3aGljaCBpdCBtaWdodCBhcHBlYXIgdGhhdCBoZSBkb2VzIGFzIGhlIGlzIHBsYXllZCBzaWduaWZpY2FudGx5IG1vcmUgaW4gbG93ZXIgZWxvcy4gQ29udmVyc2VseSwgY2hhbXBpb25zIGxpa2UgU2thcm5lciBhbmQgUmFtbXVzIGRvIHZlcnkgcG9vcmx5IHdpdGggZ29sZCBidXQgaGF2ZSBhIGhpZ2ggd2luIHJhdGUgbGlrZWx5IHRvIGRvIHRoZWlyIGFiaWxpdHkgdG8gZmVlZCBnb2xkIHRvIHRoZWlyIGxhbmVycyBhbmQgdGhlaXIgcmVsYXRpdmVseSBlYXN5IGtpdC4NCiAgNC4gT3ZlcmFsbCB3ZSBvYnNlcnZlIHRoYXQgYSBjaGFtcGlvbnMgb24gdGhlIGV4dHJlbWVzIG9mIHdpbnJhdGUgYXJlIHdlbGwgbW9kZWxlZCBieSB0aGVpciBkaWZmaWN1bHR5IC0gYW4gZW1lcmdlbnQgaHlwb3RoZXNpczogdGhlIGFkZGl0aW9uYWwgY29nbml0aXZlIGxvYWQgaW5jdXJyZWQgYnkgcGxheWluZyBhIGhpZ2ggZGlmZmljdWx0eSBjaGFtcGlvbnMgd2lsbCByZWR1Y2UgcGVyZm9ybWFuY2UgYW5kIHRodXMgYWR2ZXJzZWx5IGFmZmVjdCB3aW5yYXRlLg0KICANCiMjIyBPYnNlcnZpbmcgUGxheSByYXRlIC8gRGlmZmljdWx0eSBNZXRyaWNzDQpgYGB7cn0NCndpbnJhdGUkdGFibGVzJHRpZXIgJT4lIA0KICBmaWx0ZXIoY2hhbXBpb25OYW1lICVpbiUgYygiUmFtbXVzIiwgIk1hc3RlcllpIiwgIlNrYXJuZXIiKSkNCmNoYW1waW9ucy5zY3JhcGVkICU+JSANCiAgcmlnaHRfam9pbigNCiAgICB3aW5yYXRlJHRhYmxlcyRoaWdobG93LA0KICAgIGJ5ID0gYygibmFtZSIgPSAiY2hhbXBpb25OYW1lIikNCiAgKSAlPiUgDQogIGFycmFuZ2UoaW5kaXZpZHVhbFBvc2l0aW9uLCB3aW4pDQpgYGANCiMjIERpZmZpY3VsdHkgYW5kIFdpbnJhdGUNCmBgYHtyfQ0Kd2lucmF0ZSR0YWJsZXMkZGlmZmljdWx0eSA8LSB3aW5yYXRlJHRhYmxlcyRiYXNlICU+JSANCiAgbGVmdF9qb2luKA0KICAgIGNoYW1waW9ucy5zY3JhcGVkICU+JSANCiAgICAgIHNlbGVjdCghdGFnKSwNCiAgICBieSA9IGMoImNoYW1waW9uTmFtZSIgPSAibmFtZSIpDQogICkNCg0Kd2lucmF0ZSR0YWJsZXMkZGlmZmljdWx0eSAlPiUNCiAgZ3JvdXBfYnkoZGlmZmljdWx0eSkgJT4lIA0KICBzdW1tYXJpemUoc2QgPSBzZCh3aW5yYXRlKSwgd2lucmF0ZSA9IG1lYW4od2lucmF0ZSksIG4gPSBuKCksIC5ncm91cHMgPSAiZHJvcCIpICU+JSANCiAgbXV0YXRlKHNlbSA9IHNkIC8gc3FydChuKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBkaWZmaWN1bHR5LCB5ID0gd2lucmF0ZSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IHdpbnJhdGUgLSBzZW0sIHltYXggPSB3aW5yYXRlICsgc2VtKSwgd2lkdGggPSAuMykNCmBgYA0KIyMgQU5PVkEgYW5kIFBvc3QtSG9jIFRlc3RzDQpgYGB7cn0NCndpbnJhdGUkdGFibGVzJGRpZmZpY3VsdHkgJT4lIA0KICBhb3Yod2lucmF0ZX5kaWZmaWN1bHR5LCBkYXRhID0gLikgJT4lIA0KICBzdW1tYXJ5KCkNCiMgU28gZGlmZmVyZW5jZXMgZGVmaW5ldGx5IGV4aXN0DQoNCndpbnJhdGUkdGFibGVzJGRpZmZpY3VsdHkgJT4lIA0KICBhb3Yod2lucmF0ZX5kaWZmaWN1bHR5LCBkYXRhID0gLikgJT4lIA0KICBUdWtleUhTRCgpICU+JSANCiAgey4kZGlmZmljdWx0eX0gJT4lIA0KICBhc190aWJibGUocm93bmFtZXMgPSAiY29tcGFyaXNvbiIpIA0KYGBgDQpTbyBpbnNpZ25pZmljYW50IGRpZmZlcmVuY2VzIHdpdGggdGhlIGV4Y2VwdGlvbiBvZiB0aGUgc2V2ZXJlIGNvbXBhcmlzb25zLiBHcmFwaGljYWwgYW5hbHlzaXMgZG9lcyBzaG93IHNsaWdodCBhZHZhbnRhZ2VzIHRvIGVhc3kgY2hhbXBpb25zLiANCg0KVGFrZWF3YXlzOg0KICAxLiBBcyBhbiBBREMsIGRvbid0IHBsYXkgaHlwZXIgY2FycmllcywgeW91ciBjaGFtcCBzaG91bGQgZG8gd2VsbCB3aXRoIG9yIHdpdGhvdXQgZ29sZCBlLmcuLCBNaXNzIEZvcnR1bmUsIEFzaGUuDQogIDIuIEFzIGEganVuZ2xlciAtIGlmIHlvdSdyZSBnb2luZyB0byBwbGF5IGEgY2FycnkgY2hhbXBpb24sIG1ha2Ugc3VyZSB5b3Ugc2V0IHlvdXJzZWxmIHRvIGdldCBhaGVhZCBlYXJseSBvciBpZiB5b3UncmUgcGxheWluZyBhIHN1cHBvcnRpdmUganVuZ2xlciwgbWFrZSBzdXJlIHlvdSBnZXQgeW91ciBsYW5lcyBhaGVhZC4NCiAgMy4gQXMgYSB0b3AgbGFuZXIsIGlmIHlvdSdyZSBwbGF5aW5nIGEgaHlwZXIgY2FycnksIGNyeSB0byB5b3VyIGp1bmdsZXIgYW5kIG1pZCB0byBjYW1wIHlvdXIgbGFuZSBiZWN1YXNlIGl0J3MgZ29pbmcgdG8gYmUgYSBiYWQgdGltZSBpZiB5b3UgZ2V0IGJlaGluZC4NCiAgNC4gQXMgYSBtaWQgbGFuZXIsIHBsYXkgZWFzaWVyIGNoYW1waW9ucyBhbmQgdHJ5IHRvIGdldCB5b3VyIGxhbmVzIGFoZWFkLiBNaWQgZG9lc24ndCBjYXJyeSBhcyBtdWNoIHdoZW4geW91IGhhdmUgdG9vIG11Y2ggZ29sZCByZWxhdGl2ZSB0byB5b3VyIHRlYW0uDQogIDUuIEFzIGEgc3VwcG9ydCwgcGxheSBlYXNpZXIgY2hhbXBpb25zIGFuZCB1bmxlc3MgeW91J3JlIHBsYXlpbmcgcHlrZSwgZ2l2ZSB0aGUgZ29sZCB0byB5b3VyIHRlYW0gYW5kIHdhcmQgYSBsb3QgbW9yZS4gDQoNCiMgQ292YXJpYXRlIENoYW1waW9uIFdpbnJhdGUNCkFyZSB0aGVyZSBhbnkgcGFydGljdWxhciBjb21iaW5hdGlvbnMgb2YgY2hhbXBpb25zIHdoaWNoIGFyZSB0cnVseSBkZWdlbmVyYXRlIChzZWUgTWFzdGVyIFlpIC0gVGFyaWMgZnVubmVsaW5nIHdoaWNoIHdhcyBhIGdpZ2FudGljIGlzc3VlIGluIHByZXZpb3VzIHNlYXNvbnMpDQojIyBUZW1wb3JhcnkgVGFibGUgdG8gTWFrZSBGdW5jaXRvbiBNb3JlIEVmZmljaWVudA0KYGBge3J9DQpkYXRhLnRlbXAkY2hhbXBUZWFtcyA8LSBnYW1lSW5mby53aW4gJT4lIA0KICBzZWxlY3QobWF0Y2gsIHdpbiwgY2hhbXBpb25OYW1lKSAlPiUNCiAgZ3JvdXBfYnkobWF0Y2gsIHdpbikgJT4lIA0KICBtdXRhdGUoY2hhbXBpb24gPSByb3dfbnVtYmVyKCkpICU+JSANCiAgcGl2b3Rfd2lkZXIoDQogICAgbmFtZXNfZnJvbSA9IGNoYW1waW9uLA0KICAgIHZhbHVlc19mcm9tID0gY2hhbXBpb25OYW1lDQogICkgJT4lIA0KICBtdXRhdGUodGVhbSA9IHN0cl9jKCJfIixgMmAsYDNgLGA0YCxgNWAsIl8iLCBzZXAgPSAiXyIpKSAlPiUgDQogIHNlbGVjdChtYXRjaCwgd2luLCBjaGFtcGlvbiA9IGAxYCwgdGVhbSkgJT4lIA0KICB1bmdyb3VwKCkNCmBgYA0KIyMgRXhlY3V0aW5nIEZ1bmN0aW9uDQpgYGB7cn0NCndpbnJhdGUkdGFibGVzJGNvdmFyaWF0ZS50ZW1wIDwtIGV4cGFuZC5ncmlkKA0KICBjaGFtcGlvbnMuc2NyYXBlZCRuYW1lLA0KICBjaGFtcGlvbnMuc2NyYXBlZCRuYW1lDQopICU+JQ0KICBhcHBseSguLCAxLCBzb3J0KSAlPiUgIyBSZW1vdmluZyByb3dzIHN5bW1ldHJpY2FsbHksIG5vIG5lZWQgdG8gZG91YmxlIHNlYXJjaA0KICB0KCkgJT4lIA0KICB1bmlxdWUoKSAlPiUgDQogIGFzX3RpYmJsZSgpICU+JSANCiAgcmVuYW1lKENoYW1wMSA9IFYxLCBDaGFtcDIgPSBWMikgJT4lIA0KICBmaWx0ZXIoQ2hhbXAxICE9IENoYW1wMikgIyBSZW1vdmluZyBkaWFnb25hbCBlbGVtZW50cw0KDQojIFRoaXMgdGFrZXMgU08gZGFtbiBsb25nIHRvIHJ1biwgSSB3b25kZXIgaWYgdGhlcmUgaXMgYSBiZXR0ZXIgd2F5IHRvIGRvIHRoaXMuLi4NCiMgRG9pbmcgdGhpcyBpbiB0d28gc3RlcHMgdG8gYXZvaWQgcnVubmluZyBsb25nIGZ1bmN0aW9ucyBhZ2Fpbg0Kd2lucmF0ZSR0YWJsZXMkY292YXJpYXRlLnRlbXAyIDwtIHdpbnJhdGUkdGFibGVzJGNvdmFyaWF0ZS50ZW1wICU+JQ0KICBtdXRhdGUoDQogICAgZ2FtZUxpc3QgPSBwbWFwKA0KICAgICAgLiwNCiAgICAgIC5mID0gZnVuY3Rpb24oQ2hhbXAxLCBDaGFtcDIsIFRBQkxFID0gZGF0YS50ZW1wJGNoYW1wVGVhbXMpew0KDQogICAgICAgIFRBQkxFICU+JQ0KICAgICAgICAgIGdyb3VwX2J5KG1hdGNoLCB3aW4pICU+JQ0KICAgICAgICAgIGZpbHRlcihjaGFtcGlvbiA9PSBDaGFtcDEpICU+JSAjIE9rYXkgdGhpcyBpcyB3YXkgZmFzdGVyIGJ1dCBzdGlsbCB0dXJibyBzbG93DQogICAgICAgICAgZmlsdGVyKHN0cl9kZXRlY3QodGVhbSwgcGFzdGUwKCJfIiwgQ2hhbXAyLCAiXyIpKSkgJT4lDQogICAgICAgICAgdW5ncm91cCgpICU+JQ0KICAgICAgICAgIHJldHVybigpDQoNCiAgICAgIH0NCiAgICApDQogICkNCg0Kd2lucmF0ZSR0YWJsZXMkY292YXJpYXRlIDwtIHdpbnJhdGUkdGFibGVzJGNvdmFyaWF0ZS50ZW1wMiAlPiUgDQogIG11dGF0ZSgNCiAgICBkYXRhID0gbWFwKA0KICAgICAgZ2FtZUxpc3QsDQogICAgICAuZiA9IGZ1bmN0aW9uKGdhbWVMaXN0KXsNCiAgICAgICAgDQogICAgICAgIGdhbWVMaXN0ICU+JSANCiAgICAgICAgICBzdW1tYXJpemUod2lucmF0ZSA9IG1lYW4od2luKSwgZ2FtZXMgPSBuKCkpICU+JSANCiAgICAgICAgICByZXR1cm4oKQ0KICAgICAgICANCiAgICAgIH0NCiAgICApDQogICkgJT4lIA0KICB1bm5lc3QoY29scyA9IGMoZGF0YSkpDQoNCiMgTWFraW5nIGEgbmV3IHRhYmxlIHRvIG1ha2Ugc2VhcmNoaW5nIHBhcnRpY3VsYXIgY2hhbXBpb24gcGFpcnMgZWFzaWVyDQp3aW5yYXRlJHRhYmxlcyRjb3ZhcmlhdGUuc2VhcmNoIDwtIGJpbmRfcm93cygNCiAgd2lucmF0ZSR0YWJsZXMkY292YXJpYXRlLA0KICB3aW5yYXRlJHRhYmxlcyRjb3ZhcmlhdGUgJT4lIA0KICAgIHJlbmFtZShDaGFtcDIgPSAxLCBDaGFtcDEgPSAyKQ0KKQ0KDQoNCndpbnJhdGUkcGxvdHMkY292YXJpYXRlIDwtIHdpbnJhdGUkdGFibGVzJGNvdmFyaWF0ZSAlPiUgDQogIHBsb3RfbHkoDQogICAgeCA9IH5nYW1lcywNCiAgICB5ID0gfndpbnJhdGUsDQogICAgdGV4dCA9IH5wYXN0ZShDaGFtcDEsIENoYW1wMiwgc2VwID0gIiAiKSwNCiAgICB0eXBlID0gInNjYXR0ZXIiLA0KICAgIG1vZGUgPSAibWFya2VycyINCiAgKQ0KDQp3aW5yYXRlJHBsb3RzJGNvdmFyaWF0ZSANCmBgYA0KUHJvYmFibHkgbm90IGVub3VnaCBnYW1lcyB0byBtYWtlIGFueSBwcmVkaWN0aW9ucyAtIHdpdGggMTU1KjE1NCBjaGFtcGlvbiBkdW8gY29tYmluYXRpb25zLCB3b3VsZCBuZWVkIGEgbG90IG1vcmUgZGF0YSB0byBoYXZlIGEgZGVjZW50IHNhbXBsZSBvZiBhbGwgY29tYmluYXRpb25zLg0KDQpOZXh0LCBJJ2xsIGJlIGF0dGVtcHRpbmcgdG8gdXNlIGEgbmV1cmFsIG5ldHdvcmsgdG8gbWFrZSBwcmVkaWN0aW9ucy4=